""" Implements layout control for containers. ~~~ DO NOT USE! ~~~
    
    TODO: The contents and functionality of this module is in the
    process of being merged into other widget modules. At present, many
    parts of this module are duplicated as features of other widgets,
    so using classes defined here will likely result in redundant
    effects ocurring.

    The single most important thing contained in this module is the
    `LayoutMixin` class. This class is used as a mixin in conjunction
    with container classes to provide layout control. See `LayoutMixin`
    for more info about layout control for containers.
    
    TODO: LayoutForm
"""

from weakref import WeakKeyDictionary
from operator import attrgetter

from pygame import Rect

import sbak.base
from sbak.widget import (
    Container,
    Window,
    Desktop,
    Form
)

__all__ = [
    'LayoutMixin',
    'LayoutContainer',
    'LayoutWindow',
    'LayoutDesktop',
    #'LayoutForm',
]

class LayoutMixin(sbak.base.Object):
    """ A mixin that adds layout control to a widget class.
    
        ~~~ Description ~~~
    
        Using multiple inheritence, this class can be used as one of
        the bases for a container or window class and allow it to
        automatically resize it's contained widgets.
    
        ~~~ How To Use ~~~
        
        1.  Classes should inherit from this class BEFORE any container
        widget class:
        
        >>> class MyLayoutContainer(LayoutMixin, SomeContainer):
        
        2.  The class's constructor should first initialize
        LayoutMixin, then the container class:
        
        >>>     def __init__(self, region = None, ..., widgets=()):
        ...         LayoutMixin.__init__(self, ...)
        ...         SomeContainer.__init__(self, region, ...,
        ...                                widgets=widgets)
        
        3.  After this, widgets can be added to instances of
        `MyLayoutContainer` like any other container:
        
        >>> mlw = MyLayoutContainer()
        >>> mlw.append(widgetA)
        >>> mlw.append(widgetB)
        
        4.  You can apply per-widget layout settings using the
        `.set_widget_layout` method:
        
        >>> mlw.set_widget_layout(widgetA, rel_fit = .5)
        >>> mlw.set_widget_layout(widgetB, rel_fit = .5)
        
        5.  To apply the new settings, you must explicitly call
        `.apply_layout` once all widgets have been added. This will
        resize and/or move widgets so that they conform to the layout
        scheme:
        
        >>> mlw.apply_layout()
        
        The `apply_layout` method is NOT called automatically except
        for when the container is being resized. It must be called
        explicitly after creating the container, even if widgets are
        supplied to the constructor.
        
        Available means of layout control are documented in the
        following sections.
        
        
        ~~~ Constructor Arguments ~~~
        
        All arguments to the constructor set the attributes of the
        corresponding name in the list in the section below,
        "Container-Level Layout Control." Additionally, the following
        arguments may be used as shorthand for the values of the
        padding arguments:
        
        padding_expand_front
        
        padding_expand_back
        
        padding_fit_front
        
        padding_fit_back
        
        (Note that these are only available as arguments to the
        constructor and to certain methods but do not correspond to
        actual attributes of the same names; rather, they represent
        items of the padding attributes.)
        
        When given in the constructor or other methods, all arguments
        are "parsed" to verify that the correct value types and ranges
        are used. (These checks are currently NOT performed when the
        attributes are set directly.)
        
        
        ~~~ Container-Level Layout Control ~~~
        
        FIXME: This section should be worded more clearly.
        
        The following are layout attributes. Changes will have no
        immediate effect if set directly, but `apply_layout` will
        use them to determine how to size and position widgets.
        
        align:  Controls the edge from which widgets will appear
                according to the following rules:
                
                0 = Widgets will appear from the right. The first
                    widget will appear on the right edge, the second
                    will appear to the left of the first, and so on.
                
                1 = Widgets will appear from the top. The first widget
                    appear on the top edge, the second will appear
                    below the first, and so on.
                
                2 = Widgets will appear from the left edge. The first
                    widget will appear on the left edge, the second
                    will appear to the right of the first, and so on.
                
                3 = Widgets will appear from the bottom edge. The first
                    will appear on the bottom edge, the second will
                    appear above it, and so on.
        
        fit:    Boolean value indicating whether or not to fit
                (stretch) widgets along the "common" axis. For align
                values 0 (right) and 2 (left), the common axis is the
                vertical axis. For 1 and 3, it is the horizontal axis.
                Default is False. (Note *)
        
        expand: Boolean value indicating whether or not to expand
                (stretch) widgets along the "uncommon" axis. This is
                basically the inverse of fit. (Note *)
                Default is False.
        
        padding_fit:
                Amount of padding to put at the edges of the container
                that lie along the "fit" axis. For algin in (0,2), this
                is the vertical axis. For align in (1,3), this is the
                horizontal axis. Meaured in pixels.
        
        padding_expand:
                Amount of padding to put at the edges of the container
                that lie along the "expand" axis. For align in (0,2)
                this is the horizontal axis. For align in (1,3) this is
                the vertical axis. Measured in pixels.
        
        spacing:
                The amount of space to put between widgets. This will
                be taken into account when resizing them; the widgets
                will be shrunk proportionally to the spacing. The axis
                affected depends on the align of the container.
                Measured in pixels.
        
        Containers that inherit from LayoutMixin should usually allow
        for these attributes to be set at initialization through use of
        the `layout` constructor argument, which is a dict or other
        mapping. Example:
        
        >>> mlw = MyLayoutWidget(
        ...         layout = {
        ...             align : 2,
        ...             fit   : True
        ...         }
        ... )
        
        Notes:
        
        *   The `fit` and `expand` attributes can be overridden by
            widget-level layout control, documented below.
        
        ~~~ Widget-Level Layout Control ~~~
        
        FIXME: This section should be worded more clearly as well.
        
        After widgets have been placed into a layout-controlled
        container, any number of widget-level layout settings may be
        applied to each widget:
        
        rel_fit:    Relative fit of widget. This is a fraction in the
                    range  0.0 < rel_fit <= 1.0.  Works in conjunction
                    with the "fit" layout attribute of the container.
                    If the value is exactly 0 (default), the fit is
                    determined by the amount of usused space left after
                    space is assigned to other widgets. If it is -1,
                    the widget is not fit at all, and is not resized
                    along the fitted dimension.
                    
        rel_expand: Relative expansion of widget. It is the counterpart
                    to rel_fit, and has the same rules.
        
        margin: TODO: Document me!
        
        margin_expand: TODO: Documnet me!
        
        margin_fit: TODO: Document me!
        
        margin_expand_back: TODO: Document me!
        
        margin_expand_front: TODO: Document me!
        
        margin_fit_back: TODO: Document me!
        
        margin_fit_front: TODO: Document me!
    
        Other setting keys may be present as well, but only those
        listed above will be utilized by the layout engine.
    """
    
    def __init__(self,  align = None,
                        expand = None,
                        fit = None,
                        padding_expand = None,
                        padding_fit = None,
                        spacing = None,
                        padding_expand_front = None,
                        padding_expand_back = None,
                        padding_fit_front = None,
                        padding_fit_back = None ):
        # A WKD is used to map children to their layouts.
        # Each value is a nested dict mapping layout keywords to values. Ex:
        # _child_layouts = WeakKeyDictionary({
        #   widgetA : { rel_fit : .5,  ... },
        #   ...
        # })
        
        self._child_layouts = WeakKeyDictionary()
        
        # Set defaults.
        self.align = 0
        self.expand = False
        self.fit = False
        self.padding_expand = [0,0]
        self.padding_fit = [0,0]
        self.spacing = 0
        
        # Parse input from caller.
        self._set_layout(align, expand, fit, padding_expand, padding_fit,
                            spacing, padding_expand_front, padding_expand_back,
                            padding_fit_front, padding_fit_back)
    
    def _set_layout(self, align, expand, fit, padding_expand, padding_fit,
                            spacing, padding_expand_front, padding_expand_back,
                            padding_fit_front, padding_fit_back):
        """ Used to verify and store values for layout attributes.
            Values that are None do not get stored.
        """
        # Parse align value. Only integers in range(0,4) are allowed.
        if align is not None:
            align = int(align)
            if align not in (0,1,2,3):
                raise ValueError("align must be 0, 1, 2, or 3. (Got %i)" %
                                 align)
            self.align = align
        
        # Parse fit flag. Must be bool.
        if fit is not None:
            self.fit = bool(fit)
        
        # Parse expand flag. Must be bool.
        if expand is not None:
            self.expand = bool(expand)
        
        # Parse padding_expand. Must be an integer, or else a sequence of ints
        # with exactly two items.
        if padding_expand is not None:
            try:
                padding_expand = map(int, padding_expand[:2])
            except TypeError:
                try:
                    padding_expand = map(int, (padding_expand,)*2)
                except TypeError:
                    raise TypeError("padding_expand must be int or sequence "
                                    "of length 2. (Got %s)" %
                                    repr(padding_expand))
            self.padding_expand[:2] = padding_expand
        
        # Parse padding_fit. Must be an integer, or else a sequence of ints
        # with exactly two items.
        if padding_fit is not None:
            try:
                padding_fit = map(int, padding_fit[:2])
            except TypeError:
                try:
                    padding_fit = map(int, (padding_fit,)*2)
                except TypeError:
                    raise TypeError("padding_fit must be int or sequence "
                                    "of length 2. (Got %s)" %
                                    repr(padding_fit))
            self.padding_fit[:2] = padding_fit
        
        # Parse spacing. Must be an int.
        if spacing is not None:
            spacing = int(spacing)
            self.spacing = spacing
        
        # Parse individual values for padding_expand and padding_fit. Each must
        # be an int.
        
        if padding_expand_back is not None:
            padding_expand_back = int(padding_expand_back)
            self.padding_expand[0] = padding_expand_back
        
        if padding_expand_front is not None:
            padding_expand_front = int(padding_expand_front)
            self.padding_expand[1] = padding_expand_front
        
        if padding_fit_back is not None:
            padding_fit_back = int(padding_fit_back)
            self.padding_fit[0] = padding_fit_back
        
        if padding_fit_front is not None:
            padding_fit_front = int(padding_fit_front)
            self.padding_fit[1] = padding_fit_front
    
    def set_widget_layout(self, widget, layout=(), **kwlayout):
        """ Set the layout for a particular widget.
        
            widget:     Pass a widget that is in the container, the
                        index number of the widget, or the widget's
                        ID string.
        
            You may pass a mapping of layout keys to values and/or
            keyword arguments that map layout key names to values.
            
            See the `sbak.widget.LayoutMixin` class documentation for
            help on what layout keys are available.
        """
        layouts = self._child_layouts
        
        try:
            widget = self[widget]
        except (KeyError, IndexError, ValueError):
            if widget not in self:
                raise ValueError("No widget %r in container." % widget)
        
        if widget not in layouts:
            wlayout = layouts[widget] = {
                'rel_expand' : 0,
                'rel_fit' : 0,
                'margin_expand' : [0,0],
                'margin_fit' : [0,0]
            }
        else:
            wlayout = layouts[widget]
        
        layout = dict(layout)
        layout.update(kwlayout)
        
        for k,v in layout.iteritems():
            if k == 'margin':
                try:
                    try:
                        mlen = len(v)
                    except TypeError:
                        try:
                            v = int(v)
                        except TypeError:
                            raise TypeError
                        (wlayout['margin_expand'], wlayout['margin_fit']) = [[v]*2]*2
                    else:
                        if mlen == 2:
                            v = map(int, v)
                            (wlayout['margin_expand'], wlayout['margin_fit']) =\
                                [v[0]]*2, [v[1]]*2
                        elif mlen == 4:
                            v = map(int, v)
                            wlayout['margin_expand'] = v[0:2:1]
                            wlayout['expand_fit'] = v[1:3:1]
                        else:
                            raise TypeError
                except TypeError:
                    TypeError("'margin' requires int, or sequence of"
                                " length 2 or 4. (got %r)" % v)
                    
            elif k == 'margin_expand':
                try:
                    try:
                        mlen = len(v)
                    except TypeError:
                        v = int(v)
                        wlayout['margin_expand'] = [v]*2
                    else:
                        if mlen == 2:
                            v = map(int, v)
                        else:
                            raise TypeError
                        wlayout['margin_expand'] = v
                except TypeError:
                    raise TypeError("'margin_expand' requires int or sequence "
                                    "of 2 ints. (Got %r)" % v)
            
            elif k == 'margin_fit':
                try:
                    try:
                        mlen = int(v)
                    except TypeError:
                        v = int(v)
                        wlayout['margin_fit'] = [v]*2
                    else:
                        if mlen == 2:
                            v = map(int, v)
                        else:
                            raise TypeError
                        wlayout['margin_fit'] = v
                except TypeError:
                    raise TypeError("'margin_fit' requires int or sequence "
                                    "of 2 ints. (Got %r)" % v)
                        
            elif k == 'margin_expand_back' :
                wlayout['margin_expand'][0] = v
            
            elif k == 'margin_expand_front' :
                wlayout['margin_expand'][1] = v
            
            elif k == 'margin_fit_back' :
                wlayout['margin_fit'][0] = v
            
            elif k == 'margin_fit_front' :
                wlayout['margin_fit'][1] = v
            
            else:
                wlayout[k] = v
    
    def apply_layout(self,  align = None,
                            expand = None,
                            fit = None,
                            padding_expand = None,
                            padding_fit = None,
                            spacing = None,
                            padding_expand_front = None,
                            padding_expand_back = None,
                            padding_fit_front = None,
                            padding_fit_back = None ):
        """ Applies current layout settings to widgets.
        
            This will apply the current layout settings to widgets of
            the container, moving and resizing them as necessary in
            order to make them conform to the layout.
            
            The container-level layout settings can be passed here to
            overwrite the current settings. If any setting is not
            passed, it remains unchanged. All values will be parsed to
            verify that they are of suitable type and value.
            
            For the time being, this will need to be called every time
            widgets are added to or removed from the container in order
            to make widgets conform to the layout settings.
            
            If the container is empty, this method only stores the
            given layout settings.
        """
        
        # Method outline:
        # 0.    Store the given layout settings.
        # 1.    Miscellaneous setup
        # 2.    Determine how expand and fit map to x and y
        # 3.    Calculate expand/fit sizes for nonzero-expanded kids
        # 4.    Calculate expand/fit sizes for zero-expanded kids
        # 5.    Calculate expand/fit positions
        # 6.    Apply the new regions to contained widgets.
        
        # 0. Store the given layout settings.
        self._set_layout(align, expand, fit, padding_expand, padding_fit,
                            spacing, padding_expand_front, padding_expand_back,
                            padding_fit_front, padding_fit_back)
        
        # Do nothing if the container is empty.
        if len(self):
            
            # 1. Declare and initialize locals needed for calculating and
            # assigning the regions of widgets.
            
            # Localize some oft-needed values.
            align = self.align
            fit = self.fit
            expand = self.expand
            padding_expand = self.padding_expand
            padding_fit = self.padding_fit
            spacing = self.spacing
    
            # This dict will be filled with child widgets that have a relative
            # expand of 0, meaning that they use the "leftover" space after
            # all other widget's regions have been calculated.
            expand0s = {}
            
            # This holds the new regions for each widget.
            new_regions = [Rect(w.region) for w in self]
            
            # Total number of widgets.
            widget_count = len(self)
            
            # 2. Mapping x/y
            
            # Verify alignment value
            # (Curiously, the error message below is exactly the same as the
            # expression evaluated to raise it!)
            if align not in range(0,4):
                raise ValueError('align not in range(0,4)')
            
            # Map expand/fit axes to x/y
            axis_e = { 0:0, 1:1, 2:0, 3:1 }[align]
            axis_f = { 0:1, 1:0, 2:1, 3:0 }[align]
            
            # Expand/Fit Length Axis Number
            elan = axis_e + 2
            flan = axis_f + 2
            
            # Determine expand/fit and directions signs
            expand_dir = { 0:1, 1:0, 2:0, 3:1 }[align]
            expand_sign = { 0:-1, 1:1, 2:1, 3:-1 }[align]
            fit_dir = { 0:0, 1:1, 2:1, 3:0 }[align]
            fit_sign = { 0:1, 1:-1, 2:-1, 3:1 }[align]
            
            # Determine base region of container for widgets.
            # This is calculated as the container's occupied region shrunk by
            # padding of the layout.
            # Positions are relative to the container's actual region, so
            # base_region's position is always equal to the front paddings.
            base_region = Rect(self.region)
            base_region[axis_e] = padding_expand[expand_dir]
            base_region[axis_f] = padding_fit[fit_dir]
            base_region[elan] -= sum(padding_expand)
            base_region[flan] -= sum(padding_fit)
            
            # This is used to track how much space is left in the expand axis
            expand_unused = base_region[elan]
            
            # 3. Calculate nonzero-expand widget sizes
            # TODO: Take `spacing` into account. (Currently assumes spacing is 0.)
            
            # Repeat for every widget in the container.
            for n, w in enumerate(self):
                
                # Localize widget layout
                wlayout = self._child_layouts[w]
                
                # Only change the expand length if the expand for the container is
                # turned on.
                if expand:
                
                    # Absolutely-expanded widget calculations:
                    if wlayout['rel_expand'] < 0:
                        # Take away space equal to the widget's current size.
                        expand_unused -= w.region[elan]
                        
                        # Take away space equal to the widget's expand margin.
                        expand_unused -= sum(wlayout['margin_expand'])
                        
                        # If this is not the last widget, also take away a
                        # portion of space equal to the widget common spacing.
                        if n < widget_count:
                            expand_unused -= spacing
                    
                    # Auto-expanded widget calculations:
                    elif wlayout['rel_expand'] == 0:
                        # Don't actually do any calculating now, but note that
                        # this widget is auto-sized. (The size will be
                        # calculated later, in part 4.)
                        expand0s[n] = w
                    
                    # Relative-expanded widget calculations:
                    else:
                        # Calculate the widget expand length:
                        length = (
                            
                            # the base region's expand length, 
                            (base_region[elan]
                            
                            # times the relative expand factor of the widget,
                             * wlayout['rel_expand'])
                            
                            # minus the expand margin of the widget,
                            - sum(wlayout['margin_expand'])
                            
                            # minus the widget common spacing,
                            - spacing
                            
                            # plus some additional leftover space.
                            + (spacing / widget_count)
                        )
                        
                        # Store the calculated length for the widget.
                        new_regions[n][elan] = length
                        
                        # Take the calculated length away from the total space
                        # that is left.
                        expand_unused -= length
                        
                        # If this is not the last widget, also take away a
                        # portion of space equal to the widget common spacing.
                        if n < widget_count:
                            expand_unused -= spacing
                
                # Only calculate fit length if fitting is turned on
                if fit:
                    if wlayout['rel_fit'] >= 0:
                        if wlayout['rel_fit'] == 0:
                            rel_fit = 1
                        else:
                            rel_fit = wlayout['rel_fit']
                            
                        length = (
                            (base_region[flan] * rel_fit) - sum(wlayout['margin_fit'])
                        )
                        
                        new_regions[n][flan] = length
            
            # 4. Calculate expand-axis lengths of auto-expanded widgets
            
            # If there actually ARE any auto-expanded widgets:
            if len(expand0s):
                
                # Calculate the common length
                common_length = expand_unused / len(expand0s)
                
                # Repeat once for every 0-expanded widget
                for n, w in expand0s.iteritems():
                    # Localize widget layout
                    wlayout = self._child_layouts[w]
                    
                    # Length for each widget is the common length minus expand margin
                    # for the widget
                    length = common_length - sum(wlayout['margin_expand'])
                    
                    # Store the new length on the expand axis
                    new_regions[n][elan] = length
            
            # 5. Calculate positions
            
            # This holds the position of the current widget on the expand axis for
            # each iteration in the following loop. It is initialized to represent
            # the appropriate edge of the base region.
            expand_pos = base_region[axis_e]
            if expand_dir == 1:
                expand_pos += base_region[elan]
            
            # Base Region Fit Axis Position
            brfap = base_region[axis_f]
            if fit_dir == 1:
                brfap += base_region[flan]
            
            # Repeat once for every widget in the container.
            for n, w in enumerate(self):
                # Localize widget layout
                wlayout = self._child_layouts[w]
                
                # Calculate expand axis position.
                
                # Shift it towards the back if the direction is back-to-front
                if expand_dir:
                    expand_pos -= new_regions[n][elan]
                    
                # Shift expand_pos by the correct expand margin
                # (Add when direction is back-to-front, subtract when direction is
                # front-to-back)
                expand_pos += wlayout['margin_expand'][expand_dir] * expand_sign
                
                # Store the position.
                new_regions[n][axis_e] = expand_pos
                
                # If the direction is back-to-front, shift by the expand size
                # for the next widget
                if not expand_dir:
                    expand_pos += new_regions[n][elan]
                
                # Shift the position by the correct margin for the next widget.
                expand_pos += wlayout['margin_expand'][expand_dir] * expand_sign
                
                # If this is not the last widget, also shift by the spacing
                # for the next widget.
                if n < widget_count:
                    expand_pos += spacing * expand_sign
                
                # Calculate fit axis position.
                
                # Start with the B.R.F.A.P.
                fit_pos = brfap
                
                # Shift fit_pos by the correct fit margin
                # (Add when direction is back-to-front, subtract when direction is
                # front-to-back)
                fit_pos += wlayout['margin_fit'][fit_dir] * fit_sign
                
                # Shift it towards the back if the direction is front-to-back
                if fit_dir == 1:
                    fit_pos -= new_regions[n][flan]
                
                # Store position
                new_regions[n][axis_f] = fit_pos
        
            # 6. Apply the new regions
            
            # Repeat once for every widget in the container.
            for n, w in enumerate(self):
                w.region = new_regions[n]
    
    def append_internal(self, child, index = None, layout = (), **kwargs):
        """ Internally append or insert a widget.
        
            Aside from the required `child` argument, an optional
            mapping and/or any number of keyword arguments may be
            passed to indicate layout info specific to the widget in
            this layout. (See the `sbak.widget.LayoutMixin` class
            documentation for help on what options are available.)
        """
        super(LayoutMixin, self).append_internal(child, index)
        self.set_widget_layout(child, layout, **kwargs)
    
    def append(self, child, layout=(), **kwlayout):
        """ Append a widget into the layout-controlled container.
        
            Aside from the required `child` argument, an optional
            mapping and/or any number of keyword arguments may be
            passed to indicate layout info specific to the widget in
            this layout. (See the `sbak.widget.LayoutMixin` class
            documentation for help on what options are available.)
        """
        self.append_internal(child, None, layout, **kwlayout)
        child.set_parent_internal(self)

    def add(self, child, layout=(), **kwlayout):
        """ Add a widget to the layout-controlled container.
        
            If the child already exists, this does nothing.
        
            Aside from the required `child` argument, an optional
            mapping and/or any number of keyword arguments may be
            passed to indicate layout info specific to the widget in
            this layout. (See the `sbak.widget.LayoutMixin` class
            documentation for help on what options are available.)
        """
        try:
            self.append_internal(child, None, layout, **kwlayout)
            child.set_parent_internal(self)
        except ValueError:
            pass
    
    def insert(self, index, child, layout=(), **kwlayout):
        """ Insert a child at an arbitrary point in the container.
    
            Aside from the required `index` and `child` arguments, an
            optional mapping and/or any number of keyword arguments may
            be passed to indicate layout info specific to the widget in
            this layout. (See the `sbak.widget.LayoutMixin` class
            documentation for help on what options are available.)
        """
        self.append_internal(child, index, layout, **kwlayout)
        child.set_parent_internal(self)
    
    
    _doc_region =\
        """ Region of layout-controlled container.
        
            Works the same way as the region property of container
            superclass, except that it re-applies the current
            layout when the new region's size is different than the
            previous region's.
        """
    
    def _fset_region(self, region):
        oldregion = self.region
        super(LayoutMixin,self)._fset_region(region)
        if oldregion.size != self.region.size:
            self.apply_layout()
            

    
    def resize(self, region):
        """ Resize layout-controlled container's region.
        
            Works the same as the layout-controlled container's
            superclass, except that the layout is applied after the
            size changes.
        """
        super(LayoutMixin,self).resize(region)
        self.apply_layout()

class LayoutContainer(LayoutMixin, Container):
    """ A version of Container with support for layout control.
        
        lc = LayoutContainer(region = None, widgets = (), layout = (),
                                **kwlayout)
        
        ~~~ Constructor Arguments ~~~
        
        region:     Initial region for the container. Once a layout is
                    applied to the container, widgets will be sized and
                    positioned based on the current region size.
    
        widgets:    Widgets to place in the container initially.
        
        layout:     Pass a mapping (or anything convertable to dict) of
                    valid layout settings for the container. (See
                    `LayoutMixin` for available settings.)
        
        **kwlayout: Instead of passing a layout mapping, or in addition
                    to it, you may pass one or more keyword arguments
                    that describe layout settings.
        
        Most functionality of LayoutContainer is implemented by the
        `sbak.widget.Container` class. See its documentation for
        details.
    """
    
    def __init__(self, region = None, widgets = (), id = None, layout = (), **kwlayout):
        layout = dict(layout)
        layout.update(kwlayout)
        LayoutMixin.__init__(self, **layout)
        Container.__init__(self, region, widgets, id = id)

class LayoutWindow(LayoutMixin, Window):
    """ A version of Window with support for layout control.
    
        lw = LayoutWindow(
            region = None,
            widgets = ()
            layout = (),
            **kwlayout
        )
        
        ~~~ Constructor Arguments ~~~
        
        All arguments are the same as in `sbak.widget.Window`, but the
        following two arguments are provided as well:
        
        layout:     Pass a mapping containing layout settings. See
                    `sbak.widget.LayoutMixin` for details.
        
        **kwlayout: In addition to, or in place of the `layout`
                    argument, you may pass one or more keyword
                    arguments.
        
        Most functionality of LayoutWindow is implemented by the
        `sbak.widget.Window` class. See its documentation for
        details.
        
    """
    
    def __init__(self, region = None, widgets = (), id = None, layout = (), **kwlayout):
        layout = dict(layout)
        layout.update(kwlayout)
        LayoutMixin.__init__(self, **layout)
        Window.__init__(self, region, widgets, id = id)

class LayoutDesktop(LayoutMixin, Desktop):
    """ A version of Desktop with support for layout control.
        
        ld = LayoutDesktop(
            res=None,
            bpp=None,
            full=None,
            center=False,
            resizable=False,
            noframe=False,
            widgets=(),
            id = None,
            layout=(),
            **kwlayout
        )
        
        ~~~ Constructor Arguments ~~~
        
        All arguments are the same as with `sbak.widget.Desktop`, but
        the following two arguments are available as well:
        
        layout:     Pass a mapping containing layout settings. See
                    `sbak.widget.LayoutMixin` for details.
        
        **kwlayout: In addition to, or in place of the `layout`
                    argument, you may pass one or more keyword
                    arguments.
        
        Most functionality of LayoutDesktop is implemented by the
        `sbak.widget.Desktop` class. See its documentation for
        details.
    """
    
    def __init__(self,  res=None,
                        bpp=None,
                        full=None,
                        center=False,
                        resizable=False,
                        noframe=False,
                        widgets=(),
                        id = None,
                        layout = (),
                        **kwlayout ):
        layout = dict(layout)
        layout.update(kwlayout)
        LayoutMixin.__init__(self, **layout)
        Desktop.__init__(self, res, bpp, full, center, resizable, noframe,
                            widgets, id = id)